package cn.xiaoyu.su.service.impl;

import cn.xiaoyu.su.component.ShortLinkComponent;
import cn.xiaoyu.su.config.RabbitMQConfig;
import cn.xiaoyu.su.controller.request.ShortLinkAddRequest;
import cn.xiaoyu.su.controller.request.ShortLinkDelRequest;
import cn.xiaoyu.su.controller.request.ShortLinkPageRequest;
import cn.xiaoyu.su.controller.request.ShortLinkUpdateRequest;
import cn.xiaoyu.su.enums.DomainTypeEnum;
import cn.xiaoyu.su.enums.EventMessageType;
import cn.xiaoyu.su.enums.ShortLinkStateEnum;
import cn.xiaoyu.su.interceptor.LoginInterceptor;
import cn.xiaoyu.su.manager.DomainManager;
import cn.xiaoyu.su.manager.GroupCodeMappingManager;
import cn.xiaoyu.su.manager.LinkGroupManager;
import cn.xiaoyu.su.manager.ShortLinkManager;
import cn.xiaoyu.su.model.*;
import cn.xiaoyu.su.service.ShortLinkService;
import cn.xiaoyu.su.util.CommonUtil;
import cn.xiaoyu.su.util.IDUtil;
import cn.xiaoyu.su.util.JsonData;
import cn.xiaoyu.su.util.JsonUtil;
import cn.xiaoyu.su.vo.ShortLinkVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author ⼆当家⼩D
 * @since 2022-12-03
 */
@Service
@Slf4j
public class ShortLinkServiceImpl implements ShortLinkService {

    @Autowired
    private ShortLinkManager shortLinkManager;

    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private DomainManager domainManager;

    @Autowired
    private LinkGroupManager linkGroupManager;

    @Autowired
    private ShortLinkComponent shortLinkComponent;

    @Autowired
    private GroupCodeMappingManager groupCodeMappingManager;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    /**
     * 解析短链
     * @param shortLinkCode
     * @return
     */
    @Override
    public ShortLinkVO parseShortLinkCode(String shortLinkCode) {

        ShortLinkDO shortLinkDO = shortLinkManager.findByShortLinkCode(shortLinkCode);

        if (shortLinkDO == null) {
            return null;
        }

        ShortLinkVO shortLinkVO = new ShortLinkVO();
        BeanUtils.copyProperties(shortLinkDO,shortLinkVO);
        return shortLinkVO;
    }

    /**
     * 创建短链
     * @param shortLinkAddRequest
     * @return
     */
    @Override
    public JsonData createShortLink(ShortLinkAddRequest shortLinkAddRequest) {

        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        String newOriginalUrl = CommonUtil.addUrlPrefix(shortLinkAddRequest.getOriginalUrl());
        shortLinkAddRequest.setOriginalUrl(newOriginalUrl);

        EventMessage eventMessage = EventMessage.builder()
                .accountNo(accountNo)
                .content(JsonUtil.obj2Json(shortLinkAddRequest))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_ADD.name())
                .build();

        log.info("创建短链消息：{}",eventMessage);
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(),rabbitMQConfig.getShortLinkAddRoutingKey(),eventMessage);

        return JsonData.buildSuccess();
    }

    /**
     * 处理短链新增逻辑
     *
     * //判断短链域名是否合法
     * //判断组名是否合法
     * //生成长链摘要
     * //生成短链码
     * //加锁
     * //查询短链码是否存在
     * //构建短链对象
     * //保存数据库
     * @param eventMessage
     * @return
     */
    @Override
    public boolean handleAddShortLink(EventMessage eventMessage) {

        Long accountNo = eventMessage.getAccountNo();
        String messageType = eventMessage.getEventMessageType();

        ShortLinkAddRequest addRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkAddRequest.class);
        //短链域名校验
        DomainDO domainDO = checkDomain(addRequest.getDomainType(), addRequest.getDomainId(), accountNo);

        //组名校验
        LinkGroupDO linkGroupDO = checkLinkGroup(addRequest.getGroupId(),accountNo);

        //长链摘要
        String originalUrlDigest = CommonUtil.MD5(addRequest.getOriginalUrl());

        //短链码重复标记
        boolean duplicateCodeFlag = false;

        //生成短链码
        String shortLinkCode = shortLinkComponent.createShortLinkCode(addRequest.getOriginalUrl());

        String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]);" +
                "redis.call('expire',KEYS[1],ARGV[2]); return 1; elseif redis.call('get',KEYS[1]) == ARGV[1] then return 2;" +
                " else return 0; end;";

        Long result = redisTemplate.execute(new DefaultRedisScript<>(script,Long.class), Arrays.asList(shortLinkCode),accountNo,100);

        if (result > 0) {
            log.info("加锁成功：{}",eventMessage);
            //加锁成功
            if (EventMessageType.SHORT_LINK_ADD_LINK.name().equalsIgnoreCase(messageType)) {

                log.info("添加link:{}",messageType);

                //先判断短链码是否被占用
                ShortLinkDO shorLinkCodeDOInDB = shortLinkManager.findByShortLinkCode(shortLinkCode);

                if (shorLinkCodeDOInDB == null) {
                    //没有被占用
                    //C端处理
                    ShortLinkDO shortLinkDO = ShortLinkDO.builder().accountNo(accountNo)
                            .code(shortLinkCode)
                            .title(addRequest.getTitle())
                            .originalUrl(addRequest.getOriginalUrl())
                            .domain(domainDO.getValue())
                            .groupId(linkGroupDO.getId())
                            .expired(addRequest.getExpired())
                            .sign(originalUrlDigest)
                            .state(ShortLinkStateEnum.ACTIVE.name())
                            .del(0)
                            .build();
                    shortLinkManager.addShortLink(shortLinkDO);
                    return true;
                } else {
                    log.error("C端短链码重复：{}",eventMessage);
                    //重复标记改为true
                    duplicateCodeFlag = true;
                }


            } else if (EventMessageType.SHORT_LINK_ADD_MAPPING.name().equalsIgnoreCase(messageType)) {

                log.info("添加linkmapping:{}",messageType);
                //先判断短链码是否被占用
                GroupCodeMappingDO groupCodeMappingDOInDB = groupCodeMappingManager.findByCodeAndGroupId(shortLinkCode,linkGroupDO.getId(),accountNo);

                if (groupCodeMappingDOInDB == null) {
                    //没有被占用
                    //B端处理
                    GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                            .accountNo(accountNo)
                            .code(shortLinkCode)
                            .title(addRequest.getTitle())
                            .originalUrl(addRequest.getOriginalUrl())
                            .domain(domainDO.getValue())
                            .groupId(linkGroupDO.getId())
                            .expired(addRequest.getExpired())
                            .sign(originalUrlDigest)
                            .state(ShortLinkStateEnum.ACTIVE.name())
                            .del(0)
                            .build();
                    groupCodeMappingManager.add(groupCodeMappingDO);
                    return true;
                } else {
                    log.error("B端短链码重复：{}",eventMessage);
                    duplicateCodeFlag = true;
                }
            }

        } else {
            //加锁失败，自旋100毫秒，再调用；失败的可能是短链码被占用了，需要重新生成
            log.error("加锁失败：{}",eventMessage);

            try {TimeUnit.MILLISECONDS.sleep(100);} catch (InterruptedException e) {}

            duplicateCodeFlag = true;

        }

        if (duplicateCodeFlag) {
            String newOriginalUrl = CommonUtil.addUrlPrefixVersion(addRequest.getOriginalUrl());
            addRequest.setOriginalUrl(newOriginalUrl);
            eventMessage.setContent(JsonUtil.obj2Json(addRequest));
            log.warn("短链码保存失败，重新生成：{}",eventMessage);
            //递归调用
            handleAddShortLink(eventMessage);
        }


        return false;
    }

    /**
     * 处理更新短链消息
     * @param eventMessage
     * @return
     */
    @Override
    public boolean handleUpdateShortLink(EventMessage eventMessage) {

        log.info("处理更新短信消息:{}",eventMessage);

        long accountNo = eventMessage.getAccountNo();
        String messageType = eventMessage.getEventMessageType();


        ShortLinkUpdateRequest request = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkUpdateRequest.class);

        log.info("request:{}",request);

        //校验短链域名
        DomainDO domainDO = checkDomain(request.getDomainType(), request.getDomainId(), accountNo);

        log.info("域名：{}",domainDO);

        if (EventMessageType.SHORT_LINK_UPDATE_LINK.name().equalsIgnoreCase(messageType)) {
            //C端处理
            ShortLinkDO shortLinkDO = ShortLinkDO.builder().code(request.getCode()).title(request.getTitle())
                    .domain(domainDO.getValue()).accountNo(accountNo).build();

            log.info("C端更新：{}",shortLinkDO);

            int rows = shortLinkManager.update(shortLinkDO);

            log.debug("更新C端短链,rows={}",rows);

            return true;
        } else if (EventMessageType.SHORT_LINK_UPDATE_MAPPING.name().equalsIgnoreCase(messageType)) {
            //B端处理
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder().id(request.getMappingId())
                    .groupId(request.getGroupId())
                    .accountNo(accountNo).title(request.getTitle()).domain(domainDO.getValue()).build();

            log.info("B端更新：{}",groupCodeMappingDO);

            int rows = groupCodeMappingManager.update(groupCodeMappingDO);

            log.debug("更新B端短链,rows={}",rows);

            return true;
        }

        return false;
    }

    /**
     * 处理删除短链消息
     * @param eventMessage
     * @return
     */
    @Override
    public boolean handleDelShortLink(EventMessage eventMessage) {

        log.info("处理删除短链消息:{}",eventMessage);

        long accountNo = eventMessage.getAccountNo();

        String messageType = eventMessage.getEventMessageType();

        ShortLinkDelRequest request = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkDelRequest.class);

        if (EventMessageType.SHORT_LINK_DEL_LINK.name().equalsIgnoreCase(messageType)) {
            //C端处理
            ShortLinkDO shortLinkDO = ShortLinkDO.builder().code(request.getCode()).accountNo(accountNo).build();

            int rows = shortLinkManager.del(shortLinkDO);

            log.debug("删除C端短链,rows={}",rows);

            return true;
        } else if (EventMessageType.SHORT_LINK_DEL_MAPPING.name().equalsIgnoreCase(messageType)) {
            //B端处理
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder().id(request.getMappingId())
                    .accountNo(accountNo).groupId(request.getGroupId()).build();
            log.info("B端更新：{}",groupCodeMappingDO);

            int rows = groupCodeMappingManager.del(groupCodeMappingDO);

            log.debug("更新B端短链,rows={}",rows);

            return true;
        }

        return false;
    }

    /**
     * 分页查询短链
     *
     * 从B端查找，group_code_mapping表
     * @param pageRequest
     * @return
     */
    @Override
    public Map<String, Object> pageByGroupId(ShortLinkPageRequest pageRequest) {

        long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        Map<String, Object> resultMap = groupCodeMappingManager.pageShortLinkByGroupId(pageRequest.getPage(), pageRequest.getSize(), accountNo, pageRequest.getGroupId());

        return resultMap;
    }

    /**
     * 删除短链
     * @param request
     * @return
     */
    @Override
    public JsonData del(ShortLinkDelRequest request) {

        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        EventMessage eventMessage = EventMessage.builder()
                .accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_DEL.name())
                .build();

        //发送删除短链消息
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(),rabbitMQConfig.getShortLinkDelRoutingKey(),eventMessage);

        return JsonData.buildSuccess();
    }

    /**
     * 更新短链
     * @param request
     * @return
     */
    @Override
    public JsonData update(ShortLinkUpdateRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        EventMessage eventMessage = EventMessage.builder()
                .accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_UPDATE.name())
                .build();

        //发送更新短链消息
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(),rabbitMQConfig.getShortLinkUpdateRoutingKey(),eventMessage);

        return JsonData.buildSuccess();
    }

    /**
     * 校验组名合法性
     * @param groupId
     * @param accountNo
     * @return
     */
    private LinkGroupDO checkLinkGroup(Long groupId, Long accountNo) {

        LinkGroupDO linkGroupDO = linkGroupManager.detail(groupId, accountNo);

        Assert.notNull(linkGroupDO,"组名不合法");

        return linkGroupDO;

    }

    /**
     * 校验域名
     * @param domainType
     * @param domainId
     * @param accountNo
     * @return
     */
    private DomainDO checkDomain(String domainType, Long domainId, Long accountNo) {

        DomainDO domainDO = null;

        if (domainType.equalsIgnoreCase(DomainTypeEnum.CUSTOM.name())) {
            //自建的domain
            domainDO = domainManager.findById(domainId,accountNo);
        } else {
            //官方的domain
            domainDO = domainManager.findByDomainTypeAndId(domainId,DomainTypeEnum.OFFICIAL);
        }
        Assert.notNull(domainDO,"短链域名不合法");
        return domainDO;

    }
}
